/* 
 * This file contains mouse event callback classes for diagram handling.
 */

// ======================== MOUSE DRAG CALLBACK ========================
// 
// Class that is used to handle mouse drag events.
function MouseDragCallback() {
    this.mouseDownPosition = new Position(0, 0);
    this.mouseUpPosition = new Position(0, 0);
    this.currentPosition = new Position(0, 0);
    this.delta = new Position(0, 0);
    this.moveThreshold = 0;
    this.isDragging = false;
    this.held = false;
}
// Force this callback to end dragging. Useful if mouse leaves diagram area.
MouseDragCallback.prototype.forceEndDragging = function() {
    if (this.isDragging) {
        this.isDragging = false;
        this.evMouseUp(this.currentPosition);
    }
};
// Event handler for mouse movement. Called by the diagram when the mouse moves.
MouseDragCallback.prototype.evMouseMove = function(currentPos) {
    this.currentPosition = currentPos;
    this.delta = this.currentPosition.subtract(this.mouseDownPosition);
    var d = this.delta.length();

    if (this.held) {
        if (!this.isDragging && d >= this.moveThreshold) {
            this.isDragging = true;
            this.evDragStarted(this.mouseDownPosition);
        }

        if (this.isDragging) {
            this.evDragChanged(this.currentPosition);
        }
    }
};
// Event handler for mouse button pressed. Drag starts when mouse is moved 
// more than the set moveThreshold.
MouseDragCallback.prototype.evMouseDown = function(startPos) {
    this.mouseDownPosition = startPos;
    this.held = true;
};
// Event handler for mouse button released. Drag end callback function is 
// called if the drag initiated (moved necessery distance).
MouseDragCallback.prototype.evMouseUp = function(endPos) {
    this.mouseUpPosition = endPos;
    if (this.isDragging) {
        this.isDragging = false;
        this.evDragEnded(this.mouseUpPosition);
    }
    this.held = false;
};
// Event handler for drag start. This is called if the mouse is moved while 
// holding a button and the move threshold is just exceeded.
MouseDragCallback.prototype.evDragStarted = function(dragStartPos) {
    // nop, typically a derived class will redefine it
};
// Event handler for drag change. This is called if dragging already in 
// progress and the mouse moves.
MouseDragCallback.prototype.evDragChanged = function(dragCurrentPos) {
    // nop, typically a derived class will redefine it
};
// Event handler for drag end. Called after the mouse button has beed released 
// (only if the dragging started before).
MouseDragCallback.prototype.evDragEnded = function(dragEndPos) {
};


// ======================== CLASS GRAPHICS MOVE CALLBACK ========================
// 
// Class that is used to handle mouse drag events on a selected class 
// graphics element.
ClassGraphicsMoveCallback.prototype = new MouseDragCallback;
function ClassGraphicsMoveCallback(element) {
    this.baseClass = MouseDragCallback;
    this.baseClass();
    // ---
    this.element = element;
    this.elementBeforeDragPosition = null;
}
ClassGraphicsMoveCallback.prototype.evDragStarted = function(startPos) {
    this.constructor.prototype.evDragStarted.call(this, startPos);
    this.elementBeforeDragPosition = this.element.position;
};
ClassGraphicsMoveCallback.prototype.evDragChanged = function(currentPos) {
    this.constructor.prototype.evDragChanged.call(this, currentPos);
    this.element.moveTo(this.elementBeforeDragPosition.sum(this.delta));
};
ClassGraphicsMoveCallback.prototype.evDragEnded = function(endPos) {
    this.constructor.prototype.evDragEnded.call(this, endPos);
};

// ======================== RELATIONSHIP GRAPHICS MOVE CALLBACK ========================
// 
// Class that is used to handle mouse drag events on a selected relationship 
// graphics element.
RelationshipGraphicsMoveCallback.prototype = new MouseDragCallback;
function RelationshipGraphicsMoveCallback(element) {
    this.baseClass = MouseDragCallback;
    this.baseClass();
    // ---
    this.element = element;
    this.elementBeforeDragPosition = null;
}
RelationshipGraphicsMoveCallback.prototype.evDragStarted = function(startPos) {
    this.constructor.prototype.evDragStarted.call(this, startPos);
    this.elementBeforeDragPosition = this.element.position;
};
RelationshipGraphicsMoveCallback.prototype.evDragChanged = function(currentPos) {
    this.constructor.prototype.evDragChanged.call(this, currentPos);
    this.element.moveTo(this.elementBeforeDragPosition.sum(this.delta));
};
RelationshipGraphicsMoveCallback.prototype.evDragEnded = function(endPos) {
    this.constructor.prototype.evDragEnded.call(this, endPos);
};

// ======================== DIAGRAM VIEW DRAG CALLBACK ========================
// 
// Class that is used to handle mouse drag events on the current diagram's view.
DiagramViewDragCallback.prototype = new MouseDragCallback;
function DiagramViewDragCallback(diagram) {
    this.baseClass = MouseDragCallback;
    this.baseClass();
    // ---
    this.diagram = diagram;
    this.diagramViewBeforeDragPosition = null;
}
DiagramViewDragCallback.prototype.evDragStarted = function(startPos) {
    this.constructor.prototype.evDragStarted.call(this, startPos);
    this.diagramViewBeforeDragPosition = this.diagram.viewPosition;
};
DiagramViewDragCallback.prototype.evDragChanged = function(currentPos) {
    this.constructor.prototype.evDragChanged.call(this, currentPos);

    var d = this.delta;
    d.x *= -1;
    d.y *= -1;
    var newViewPos = this.diagramViewBeforeDragPosition.sum(d);
    newViewPos.x = Clamp(newViewPos.x, 0, this.diagram.diagramDim.x - this.diagram.viewDim.x);
    newViewPos.y = Clamp(newViewPos.y, 0, this.diagram.diagramDim.y - this.diagram.viewDim.y);
    this.diagram.moveViewTo(newViewPos);
};
DiagramViewDragCallback.prototype.evDragEnded = function(endPos) {
    this.constructor.prototype.evDragEnded.call(this, endPos);
};

// ======================== CREATE RELATIONSHIP MOVE CALLBACK ========================
// 
// Class that is used to handle mouse drag events while waiting for the user 
// to create a relationship between classes.
CreateRelationshipCallback.prototype = new MouseDragCallback;
function CreateRelationshipCallback(newRelationshipModel, diagram, model) {
    this.baseClass = MouseDragCallback;
    this.baseClass();
    // ---
    this.relationshipModel = newRelationshipModel;
    this.diagram = diagram;             // diagram to be placed on
    this.model = model;                 // model to be inserted into
    this.sourceDiagramElement = null;   // source graphic element
    this.targetDiagramElement = null;   // target graphic element
}
CreateRelationshipCallback.prototype.evDragStarted = function(startPos) {
    this.constructor.prototype.evDragStarted.call(this, startPos);

    this.sourceDiagramElement = null;
    this.targetDiagramElement = null;

    var e = this.diagram.getElementByPosition(this.mouseDownPosition, ClassGraphics);
    if (e !== null) {
        this.sourceDiagramElement = e;
    } else {
        // FIXME
    }
};
CreateRelationshipCallback.prototype.evDragChanged = function(currentPos) {
    this.constructor.prototype.evDragChanged.call(this, currentPos);
};
CreateRelationshipCallback.prototype.evDragEnded = function(endPos) {
    this.constructor.prototype.evDragEnded.call(this, endPos);

    var e = this.diagram.getElementByPosition(this.mouseUpPosition, ClassGraphics);
    if (e !== null) {
        this.targetDiagramElement = e;
    } else {
        // FIXME
    }

    if (this.sourceDiagramElement !== null && this.targetDiagramElement !== null) {

        var srcClass = this.sourceDiagramElement.model;
        var trgClass = this.targetDiagramElement.model;

        if(this.relationshipModel instanceof Inheritance) {
            if(this.relationshipModel.isGeneralization) {
                if(srcClass.isInterface !== trgClass.isInterface) {
                    // generalization between an interface and a class
                    alert("Generalization can only be added between classes or between interfaces.");
                    return;
                }
            } if(this.relationshipModel.isRealization) {
                if((srcClass.isInterface) || (!trgClass.isInterface)) {
                    // target (base) is not an interface or
                    // source (derived) is not a class
                    alert("Realization can only be added between a class and an interface. (Direction matters.)");
                    return;
                }
            } else {
                // TODO error
            }
        }

        var newGraphicsElement = new RelationshipGraphics(this.relationshipModel, this.sourceDiagramElement, this.targetDiagramElement);

        this.relationshipModel.setSourceEnd(srcClass);
        this.relationshipModel.setTargetEnd(trgClass);

        // Note: relationships are automatically added to the relationship ends 
        //       by the model when adding the relationship itself

        project.addModelElement(this.relationshipModel);
        // add defaultly added stereotypes to the model
        for(var i=0; i<this.relationshipModel.stereotypes.length; i++) {
            project.addModelElement(this.relationshipModel.stereotypes[i]);
        }

        this.diagram.addElement(newGraphicsElement);
        this.diagram.setSelectedElement(newGraphicsElement);

    }
};



// ======================== MOUSE CLICK CALLBACK ========================
// 
// Class that is used to handle mouse drag events.
function MouseClickCallback() {
}
// Event handler for mouse clicking. Called by the diagram when the user clicks 
// on the diagram while this callback is active.
MouseClickCallback.prototype.evMouseClick = function(currentPos) {
    // nop, typically a derived class will redefine it
};

// ======================== CREATE CLASS CALLBACK ========================
// 
// Class that is used to handle mouse drag events while waiting for the user 
// to create a relationship between classes.
CreateClassCallback.prototype = new MouseClickCallback;
function CreateClassCallback(newClassModel, diagram, model) {
    this.baseClass = MouseClickCallback;
    this.baseClass();
    // ---
    this.classModel = newClassModel;
    this.diagram = diagram;             // diagram to be placed on
    this.model = model;                 // model to be inserted into
}
// Event handler for mouse clicking. Called by the diagram when the user clicks 
// on the diagram while this callback is active.
CreateClassCallback.prototype.evMouseClick = function(currentPos) {
    
    var newGraphicsElement = new ClassGraphics(currentPos, this.classModel);
    
    project.addModelElement(this.classModel);
    
    // add defaultly added stereotypes to the model
    for(var i=0; i<this.classModel.stereotypes.length; i++) {
        project.addModelElement(this.classModel.stereotypes[i]);
    }

    this.diagram.addElement(newGraphicsElement);
    this.diagram.setSelectedElement(newGraphicsElement);
};

